/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - core/memory
// Copyright( c) 2013.  All Rights Reserved
//
// File:		AEFreeListAllocator.cpp
// Author:		Gianluca Belardelli
// Date:		03/10/2013
//
/////////////////////////////////////////////////////////////////////////
#include <AECore.h>
//#include <memory\AEFreeListAllocator.h>
#include <core\memory\AEMallocAllocator.h>

// Defalut 128k
#define DEFAULT_POOL_SIZE	128*1024

AEFreeListAllocator::AEFreeListAllocator( void )
{
	m_uiPoolSize = DEFAULT_POOL_SIZE;
	m_uiAllocSize = 0;
	m_lpParentAllocator = &AEMallocAllocator::GetInstance();
	m_lpFreeBlocks = (AEFreeBlock *)m_lpParentAllocator->BlockAlloc( m_uiPoolSize + sizeof(AEMemStats) + 16 );
	m_lpBlockStart = (AEUINT8 *)m_lpFreeBlocks;

	AEASSERT( m_lpFreeBlocks );

	AEUINT8 *lpPtr = (AEUINT8 *)m_lpFreeBlocks;

	AEMemStats ms;
	ms.uiAllocated = DEFAULT_POOL_SIZE;
	ms.uiAvailable = DEFAULT_POOL_SIZE;
	ms.uiInUse = 0;
	ms.uiLargestBlock = 0;
	ms.uiPeakInUse = 0;
	ms.uiTotalAllocated = DEFAULT_POOL_SIZE;

	memcpy( lpPtr, &ms, sizeof( AEMemStats ) );
	lpPtr += sizeof( AEMemStats );

	strcpy( (char *)lpPtr, "FreeListAlloc");
	lpPtr+=16;

	m_lpFreeBlocks = (AEFreeBlock *)lpPtr;
	m_lpFreeBlocks->uiSize = DEFAULT_POOL_SIZE;
	m_lpFreeBlocks->lpNextBlock = 0L;
}

AEFreeListAllocator::AEFreeListAllocator( AEUINT32 uiSize, AEMemoryAllocator *lpParentAllocator )
{
	m_uiPoolSize = uiSize;
	m_uiAllocSize = 0;

	if( lpParentAllocator == 0L )
		m_lpParentAllocator = &AEMallocAllocator::GetInstance();
	else
		m_lpParentAllocator = lpParentAllocator;

	m_lpFreeBlocks = (AEFreeBlock *)m_lpParentAllocator->BlockAlloc( uiSize + sizeof(AEMemStats) + 16 );
	m_lpBlockStart = (AEUINT8 *)m_lpFreeBlocks;
	AEASSERT( m_lpFreeBlocks );

	AEUINT8 *lpPtr = (AEUINT8 *)m_lpFreeBlocks;
	AEMemStats ms;
	ms.uiAllocated = uiSize;
	ms.uiAvailable = uiSize;
	ms.uiInUse = 0;
	ms.uiLargestBlock = 0;
	ms.uiPeakInUse = 0;
	ms.uiTotalAllocated = uiSize;

	memcpy( lpPtr, &ms, sizeof( AEMemStats ) );
	lpPtr += sizeof( AEMemStats );

	strcpy( (char *)lpPtr, "FreeListAlloc");
	lpPtr+=16;

	m_lpFreeBlocks = (AEFreeBlock *)lpPtr;
	m_lpFreeBlocks->uiSize = uiSize;
	m_lpFreeBlocks->lpNextBlock = 0L;

	DEBUG_MSG("FreeListAlloc created: size %d at 0x%p", uiSize, m_lpFreeBlocks );
}

void *AEFreeListAllocator::BlockAlloc( AEUINT32 uiNumBytes, AEMemoryAlign maAlign )
{
	AEASSERT_MSG( uiNumBytes > 0, "Allocazione di 0 bytes non consentita" );
	
	m_uiAllocSize = uiNumBytes;

	AEFreeBlock* lpPrevFreeBlock = 0L;
	AEFreeBlock* lpFreeBlock     = m_lpFreeBlocks;

	while( lpFreeBlock )
	{
		//Calculate adjustment needed to keep object correctly aligned
		void *lpPtr = (void *)( (AEUINT32)lpFreeBlock + sizeof(AEFreeBlock) );
		AEUINT8 uiAdjustment = alignAdjustWithHeader( lpPtr, maAlign, sizeof( AEAllocationHeader ) );
		AEUINT32 uiTotalSize = uiNumBytes + uiAdjustment + sizeof( AEAllocationHeader );

		//If allocation doesn't fit in this FreeBlock, try the next
		if( lpFreeBlock->uiSize < uiTotalSize )
		{
			lpPrevFreeBlock = lpFreeBlock;
			lpFreeBlock = lpFreeBlock->lpNextBlock;
			continue;
		}

		//If allocations in the remaining memory will be impossible
		if( lpFreeBlock->uiSize - uiTotalSize <= sizeof( AEAllocationHeader ) )
		{
			//Increase allocation size instead of creating a new FreeBlock
			uiTotalSize = lpFreeBlock->uiSize;

			if( lpPrevFreeBlock != 0L )
				lpPrevFreeBlock->lpNextBlock = lpFreeBlock->lpNextBlock;
			else
				m_lpFreeBlocks = lpFreeBlock->lpNextBlock;
		}
		else
		{
			//Else create a new FreeBlock containing remaining memory
			AEFreeBlock *lpNextBlock = (AEFreeBlock*)( (AEUINT8 *)lpFreeBlock + uiTotalSize + sizeof(AEFreeBlock) );
			lpNextBlock->uiSize = lpFreeBlock->uiSize - uiTotalSize;
			lpNextBlock->lpNextBlock = lpFreeBlock->lpNextBlock;
			
			if( lpPrevFreeBlock != 0L )
				lpPrevFreeBlock->lpNextBlock = lpNextBlock;
			else
				m_lpFreeBlocks = lpNextBlock;
		}

		//AEUINT8 *lpAlignedAddress = (AEUINT8 *)lpFreeBlock + uiAdjustment;
		AEUINT8 *lpAlignedAddress = (AEUINT8 *)lpPtr + uiAdjustment + sizeof( AEAllocationHeader );

		#ifdef AEDEBUG
			if( (AEUINT32)lpAlignedAddress & 0xF )
			{
				DEBUG_MSG("FreeListAlloc not aligned address at 0x%p", lpAlignedAddress );
			}
		#endif

		AEAllocationHeader *lpHeader = (AEAllocationHeader *)((AEUINT32)(lpAlignedAddress)-sizeof(AEAllocationHeader));
		lpHeader->uiBlockSize = uiTotalSize;
		lpHeader->uiAdjBytes = uiAdjustment;

		AEMemStats *lpStats = (AEMemStats *)m_lpBlockStart;

		lpStats->uiAvailable-=uiTotalSize;
		lpStats->uiInUse+=uiTotalSize;

		if( lpStats->uiLargestBlock < uiTotalSize )
			lpStats->uiLargestBlock = uiTotalSize;

		DEBUG_MSG("FreeListAlloc %d bytes at 0x%p", uiTotalSize, lpAlignedAddress );
		#ifdef AEDEBUG
		memset( lpAlignedAddress, 0xAA, uiNumBytes );
		#endif
		return (void*)lpAlignedAddress;
	}

	AEASSERT_MSG( false, "Allocazione fallita per mancanza di blocchi sufficentemente capienti." );

	return 0L;
}

void AEFreeListAllocator::BlockFree( void *lpPtr, AEUINT32 uiNumBytes )
{
	AEAllocationHeader *lpHeader = (AEAllocationHeader *)( (AEUINT8*)lpPtr - sizeof( AEAllocationHeader ) );

	AEUINT32 uiSize = lpHeader->uiBlockSize;

	AEUINT8 *lpBlockStart = (AEUINT8 *)lpHeader - lpHeader->uiAdjBytes -sizeof(AEFreeBlock);
	AEUINT8 *lpBlockEnd   = lpBlockStart + uiSize;
	AEUINT32 uiBlockSize   = uiSize;

	AEFreeBlock *lpPrevFreeBlock = 0L;
	AEFreeBlock *lpFreeBlock = m_lpFreeBlocks;

	while( lpFreeBlock != 0L )
	{
		if( (AEUINT8*)lpFreeBlock >= lpBlockEnd )
			break;

		lpPrevFreeBlock = lpFreeBlock;
		lpFreeBlock = lpFreeBlock->lpNextBlock;
	}

	if( lpPrevFreeBlock == 0L )
	{
		lpPrevFreeBlock = (AEFreeBlock*)lpBlockStart;
		lpPrevFreeBlock->uiSize = uiBlockSize;
		lpPrevFreeBlock->lpNextBlock = m_lpFreeBlocks;

		m_lpFreeBlocks = lpPrevFreeBlock;
	}
	else if( (AEUINT8*)lpPrevFreeBlock + lpPrevFreeBlock->uiSize == lpBlockStart )
	{
		lpPrevFreeBlock->uiSize += uiBlockSize;
	}
	else
	{
		AEFreeBlock *lpTemp = (AEFreeBlock*)lpBlockStart;
		lpTemp->uiSize = uiBlockSize;
		lpTemp->lpNextBlock = lpPrevFreeBlock->lpNextBlock;
		lpPrevFreeBlock->lpNextBlock = lpTemp;

		lpPrevFreeBlock = lpTemp;
	}

	if( lpFreeBlock != 0L && (AEUINT8 *)lpFreeBlock == lpBlockEnd )
	{
		lpPrevFreeBlock->uiSize += lpFreeBlock->uiSize;
		lpPrevFreeBlock->lpNextBlock = lpFreeBlock->lpNextBlock;
	}

	DEBUG_MSG("FreeListFree %d bytes at 0x%p", uiBlockSize, lpPtr );
	#ifdef AEDEBUG
		memset( lpHeader, 0xDE, uiNumBytes + sizeof( AEAllocationHeader ) );
	#endif

	AEMemStats *lpStats = (AEMemStats *)m_lpBlockStart;

	lpStats->uiAvailable+=uiSize;
	lpStats->uiInUse-=uiSize;

/*
	bool bBlockMerged = false;

	//Find adjacent free blocks and merge
	bool bSearch = true;

	while( bSearch )
	{
		bSearch = false;

		AEFreeBlock *lpPrevFreeBlock = 0L;
		AEFreeBlock *lpFreeBlock = m_lpFreeBlocks;

		while( lpFreeBlock != 0L )
		{
			if( (AEUINT8*)lpFreeBlock + lpFreeBlock->uiSize == lpBlockStart )
			{
				lpFreeBlock->uiSize += uiBlockSize;

				lpBlockStart = (AEUINT8*)lpFreeBlock;
				lpBlockEnd   = lpBlockStart + lpFreeBlock->uiSize;
				uiBlockSize  = lpFreeBlock->uiSize;

				bSearch = true;
				bBlockMerged = true;
				break;
			}
			else if( lpBlockEnd == (AEUINT8*)lpFreeBlock )
			{
				AEFreeBlock *lpNewFreeBlock = (AEFreeBlock*)lpBlockStart;
				lpNewFreeBlock->lpNextBlock = lpFreeBlock->lpNextBlock;
				lpNewFreeBlock->uiSize = uiBlockSize + lpFreeBlock->uiSize;

				if( lpFreeBlock == m_lpFreeBlocks )
					m_lpFreeBlocks = lpNewFreeBlock;
				else if( lpPrevFreeBlock != lpNewFreeBlock )
					lpPrevFreeBlock->lpNextBlock = lpNewFreeBlock;

				lpBlockStart = (AEUINT8 *)lpNewFreeBlock;
				lpBlockEnd   = lpBlockStart + lpNewFreeBlock->uiSize;
				uiBlockSize  = lpNewFreeBlock->uiSize;

				bSearch = true;
				bBlockMerged = true;
				break;
			}

			lpPrevFreeBlock = lpFreeBlock;
			lpFreeBlock = lpFreeBlock->lpNextBlock;
		}
	}

	if( !bBlockMerged )
	{
		AEFreeBlock *lpBlock = (AEFreeBlock*)( (AEUINT8 *)lpPtr - sizeof( AEAllocationHeader ) );
		lpBlock->uiSize      = uiBlockSize;
		lpBlock->lpNextBlock = m_lpFreeBlocks;
		m_lpFreeBlocks       = lpBlock;
	}
	
	memset( lpPtr, 0xCD, uiNumBytes );

	AEMemStats *lpStats = (AEMemStats *)m_lpBlockStart;

	lpStats->uiAvailable+=uiSize;
	lpStats->uiInUse-=uiSize;
	*/
}

void AEFreeListAllocator::_expandPoolSize( void )
{
/*	size_t size = ( m_nAllocSize > sizeof(FreeStore*) ) ? m_nAllocSize : sizeof(FreeStore*);
	
	FreeStore* head = reinterpret_cast <FreeStore*> (new char[size]);
	//FreeStore* head = reinterpret_cast <FreeStore*> (aligned_malloc(size, 16));
	
	m_lpFreeStoreHead = head;

	for (int i = 0; i < m_nPoolSize; i++)
	{
		head->next = reinterpret_cast <FreeStore*> (new char [size]);
		//head->next = reinterpret_cast <FreeStore*> (aligned_malloc(size, 16));
		head = head->next;
	}

	head->next = 0;*/
}

void AEFreeListAllocator::_cleanUp( void )
{
/*	FreeStore* nextPtr = m_lpFreeStoreHead;
	for( ; nextPtr; nextPtr = m_lpFreeStoreHead )
	{
		m_lpFreeStoreHead = m_lpFreeStoreHead->next;
		delete [] nextPtr; // remember this was a char array
		//aligned_free( nextPtr );
	}*/
}

void AEFreeListAllocator::GetMemoryStats( AEMemStats &msMemStats )
{
	memcpy( &msMemStats, &m_lpBlockStart, sizeof( AEMemStats ) );
}

AEINT32 AEFreeListAllocator::GetAllocatedSize( const void *lpObj, AEINT32 nBytes )
{
	return 1;
}
